#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/err.h>
#include <linux/compiler.h>
#include <asm/io.h>
#include <asm/uaccess.h>


#define CTRL_LED_CMD    (0x01)

static int dev_open(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char __user *, size_t , loff_t *);
static ssize_t dev_write(struct file *, const char __user *, size_t , loff_t *);
static int dev_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static int dev_release(struct inode *, struct file *);



struct mydev
{
	unsigned int 			major;
	char 					*dev_name;
	char 					*cls_name;
	char 					*cls_dev_name;
	struct class 			*cls;
	int 					flags;
	struct file_operations  dev_oper;
    volatile unsigned long  phy_addr;
    volatile unsigned long  *virt_addr;
    unsigned int            addr_len;
    volatile unsigned long  *gpfcon;
    volatile unsigned long  *gpfdat;
    int                     dev_count;
    struct class_device 	*cls_dev[];
};

static struct mydev dev = {
    .major    = 0,
	.dev_name = "leds_dev",         //cat /proc/devices 可以通过此名找打分配的主设备号
	.cls_name = "leds",             //创建目录：/sys/class/leds
	.cls_dev_name = "led",          //创建目录：/sys/class/leds/led , mdev会用这个名字创建设备节点名/dev/led
	.cls = NULL,

	.flags = 0,
	.dev_oper = {
		.owner		= THIS_MODULE,
		.open 		= dev_open,
		.read 		= dev_read,
		.write 		= dev_write,
		.ioctl      = dev_ioctl,
		.release	= dev_release, 
	},

    .phy_addr       = 0x56000050,
    .virt_addr      = NULL,
    .addr_len        = 16,
    .cls_dev = {NULL, NULL, NULL, NULL},
	.dev_count = 4,
};

typedef struct devinfo
{
    unsigned int which;
    unsigned int onoff;
}devinfo;



static void leds_onoff(int minor, int onoff)
{
    printk(KERN_INFO "led[%d], %s\n", minor, onoff == 0 ? "on" : "off");
    if(minor == 0)
        (onoff == 0)? (*dev.gpfdat &= ~(1 << 4)) : (*dev.gpfdat |= (1 << 4));
    else if(minor == 1)
        (onoff == 0)? (*dev.gpfdat &= ~(1 << 5)) : (*dev.gpfdat |= (1 << 5));
    else if(minor == 2)
        (onoff == 0)? (*dev.gpfdat &= ~(1 << 6)) : (*dev.gpfdat |= (1 << 6));
    else if(minor == 3)
        (onoff == 0)? (*dev.gpfdat &= ~((1 << 4) | (1 << 5) | (1 << 6))) : (*dev.gpfdat |= ((1 << 4) | (1 << 5) | (1 << 
        6)));
}

/*
 * @func: device open
 */
static int dev_open(struct inode *node, struct file *filep)
{
    int dev_minor = MINOR(node->i_rdev);
    printk(KERN_INFO "open led[%d] device\n", dev_minor);
    if(dev_minor == 0)//dev/led0
    {
        *dev.gpfcon &= ~(3 << 8);
        *dev.gpfcon |= (1 << 8);
        *dev.gpfdat |= (1 << 4);
    }
    else if(dev_minor == 1)//dev/led1
    {
        *dev.gpfcon &= ~(3 << 10);
        *dev.gpfcon |= (1 << 10);
        *dev.gpfdat |= (1 << 5); 
    }
    else if(dev_minor == 2)//dev/led2
    {
        *dev.gpfcon &= ~(3 << 12);
        *dev.gpfcon |= (1 << 12);
        *dev.gpfdat |= (1 << 6); 
    }
    else if(dev_minor == 3)//dev/led3
    {
        *dev.gpfcon &= ~((3 << 8) | (3 << 10) | (3 << 12));  //将GPF4、GPF5、GPF6清零
        *dev.gpfcon |= ((1 << 8) | (1 << 10) | (1 << 12));  //将GPF4、GPF5、GPF6配置为输出
        *dev.gpfdat |= ((1 << 4) | (1 << 5) | (1 << 6));     //将GPF4、GPF5、GPF6拉高，使LED默认为熄灭状态 
    }
    
	return 0;
}

/*
 * @func: device read
 */
static ssize_t dev_read(struct file *filep, char __user *buffer, size_t size, loff_t *offset)
{
	printk(KERN_INFO "read device\n");
	return 0;
}

/*
 * @func: device write
 */
static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t size, loff_t *offset)
{
	printk(KERN_INFO "write device\n");
	return 0;
}

/*
 * device ioctl
 */
static int dev_ioctl(struct inode *node, struct file *filep, unsigned int cmd, unsigned long arg)
{
    int dev_minor = MINOR(node->i_rdev);
    printk(KERN_INFO "ioctl led%d device\n", dev_minor);
    switch(cmd)
    {
        case CTRL_LED_CMD:
        {
            printk(KERN_INFO "ioctl led cmd\n");
            int *onoff =(int *)arg;
            leds_onoff(dev_minor, *onoff);
            break;
        }
        default:
            break;
    }
    return 0;
}


/*
 * @func: device close
 */

static int dev_release(struct inode *node, struct file *filep)
{
	printk(KERN_INFO "release led[%d]\n", MINOR(node->i_rdev));
	return 0;
}



static int __init leddrv_init(void)
{
    printk(KERN_INFO "The driver 'led_drv' is inserted!\n");
	//[0]. 注册一个字符设备
 	dev.major = register_chrdev(0, dev.dev_name, &dev.dev_oper);
    if(dev.major < 0)
	{
		printk(KERN_ERR "Register char device[%s] failed", dev.dev_name);
		goto ERR;
	}
	//[1]. 将字符设备信息注册到sys文件系统中
	dev.cls = class_create(THIS_MODULE, dev.cls_name);
	if(IS_ERR(dev.cls))
	{
		printk(KERN_ERR "Create class [%s] failed", dev.cls_name);
		unregister_chrdev(dev.major, dev.dev_name);
		goto ERR;
	}
    int i;
    for(i = 0; i < dev.dev_count; ++i)
    {
	    dev.cls_dev[i] = class_device_create(dev.cls, NULL, MKDEV(dev.major, i), NULL, "%s%d", dev.cls_dev_name, i);
	    if(unlikely(IS_ERR(dev.cls_dev[i])))
	    {
            printk(KERN_ERR "Create class device [%s%d] failed", dev.cls_dev_name, i);
            int j;
            for(j = i - 1; j > 0; --j)
                class_device_unregister(dev.cls_dev[j]);
            unregister_chrdev(dev.major, dev.dev_name);
	        class_destroy(dev.cls);
            goto ERR;
        }
    }
    
    //[2]. 将LEDS的物理地址映射到虚拟地址
    dev.virt_addr =  (volatile unsigned long *)ioremap(dev.phy_addr, dev.addr_len);
    if(!dev.virt_addr)
    {
        printk(KERN_ERR "Map the leds's physical address to virtual address failed.");
        goto ERR;
    }
    //[3]. 初始化LED引脚
    dev.gpfcon = dev.virt_addr;
    dev.gpfdat = dev.virt_addr + 1;
    return 0;
ERR:
    printk(KERN_ERR "The driver 'led_drv' register failed\n");

	dev.flags = -1;
	return -1;
}
static void __exit leddrv_exit(void)
{
    printk(KERN_INFO "The driver 'led_drv' is removed!\n");
	if(!dev.flags)
	{
		unregister_chrdev(dev.major, dev.dev_name);
        int i;
        for(i = 0; i < dev.dev_count; ++i)
            class_device_unregister(dev.cls_dev[i]);
		class_destroy(dev.cls);
	}
    if(dev.virt_addr)
    {
        iounmap(dev.virt_addr);
        dev.virt_addr = NULL;
    }
}



module_init(leddrv_init);
module_exit(leddrv_exit);


MODULE_LICENSE("GPL");

